#include "../../includes/NeonMath/Vector3.hpp"
using namespace Neon;

Vector3 Vector3::backward = Vector3(0.0f, 0.0f, 1.0f);
Vector3 Vector3::down = Vector3(0.0f, -1.0f, 0.0f);
Vector3 Vector3::forward = Vector3(0.0f, 0.0f, -1.0f);
Vector3 Vector3::left = Vector3(-1.0f, 0.0f, 0.0f);
Vector3 Vector3::one = Vector3(1.0f, 1.0f, 1.0f);
Vector3 Vector3::right = Vector3(1.0f, 0.0f, 0.0f);
Vector3 Vector3::up = Vector3(0.0f, 1.0f, 0.0f);
Vector3 Vector3::xUnit = Vector3(1.0f, 0.0f, 0.0f);
Vector3 Vector3::yUnit = Vector3(0.0f, 1.0f, 0.0f);
Vector3 Vector3::zero = Vector3(0.0f, 0.0f, 0.0f);
Vector3 Vector3::zUnit = Vector3(0.0f, 0.0f, 1.0f);

Vector3::Vector3()
	: X(0.0f), Y(0.0f), Z(0.0f)
{ }

Vector3::Vector3(const nfloat x, const nfloat y, const nfloat z)
	: X(x), Y(y), Z(z)
{ }

Vector3::Vector3(const Neon::Vector3 &vec)
	: X(vec.X), Y(vec.Y), Z(vec.Z)
{ }

Vector3&
Vector3::operator =(const Vector3 &rhs)
{
	X = rhs.X;
	Y = rhs.Y;
	Z = rhs.Z;

	return *this;
}

Vector3
Vector3::operator +() const
{
	return Vector3(+X, +Y, +Z);
}

Vector3
Vector3::operator +(const Vector3 &rhs) const
{
	return Vector3(X + rhs.X, Y + rhs.Y, Z + rhs.Z);
}

Vector3
Vector3::operator +(nfloat rhs) const
{
	return Vector3(X + rhs, Y + rhs, Z + rhs);
}

Vector3&
Vector3::operator +=(const Vector3 &rhs)
{
	(*this) = (*this) + rhs;

	return *this;
}

Vector3&
Vector3::operator +=(nfloat rhs)
{
	(*this) = (*this) + rhs;

	return *this;
}

Vector3
Vector3::operator -() const
{
	return Vector3(-X, -Y, -Z);
}

Vector3
Vector3::operator -(const Vector3 &rhs) const
{
	return Vector3(X - rhs.X, Y - rhs.Y, Z - rhs.Z);
}

Vector3
Vector3::operator -(nfloat rhs) const
{
	return Vector3(X - rhs, Y - rhs, Z - rhs);
}

Vector3&
Vector3::operator -=(const Vector3 &rhs)
{
	(*this) = (*this) - rhs;

	return *this;
}

Vector3&
Vector3::operator -=(nfloat rhs)
{
	(*this) = (*this) - rhs;

	return *this;
}

Vector3
Vector3::operator *(const Vector3 &rhs) const
{
	return Vector3(X * rhs.X, Y * rhs.Y, Z * rhs.Z);
}

Vector3
Vector3::operator *(nfloat rhs) const
{
	return Vector3(X * rhs, Y * rhs, Z * rhs);
}

Vector3&
Vector3::operator *=(const Vector3 &rhs)
{
	(*this) = (*this) * rhs;

	return *this;
}

Vector3&
Vector3::operator *=(nfloat rhs)
{
	(*this) = (*this) * rhs;

	return *this;
}

Vector3
Vector3::operator /(const Vector3 &rhs) const
{
	return Vector3(X / rhs.X, Y / rhs.Y, Z / rhs.Z);
}

Vector3
Vector3::operator /(nfloat rhs) const
{
	rhs = 1.0f / rhs;
	return Vector3(X * rhs, Y * rhs, Z * rhs);
}

Vector3&
Vector3::operator /=(const Vector3 &rhs)
{
	(*this) = (*this) / rhs;

	return *this;
}

Vector3&
Vector3::operator /=(nfloat rhs)
{
	(*this) = (*this) / rhs;

	return *this;
}

bool
Vector3::operator ==(const Vector3 &rhs) const
{
	if(X == rhs.X && Y == rhs.Y && Z == rhs.Z) {
		return true;
	}

	return false;
}

bool
Vector3::operator !=(const Vector3 &rhs) const
{
	if(X != rhs.X || Y != rhs.Y || Z != rhs.Z) {
		return true;
	}

	return false;
}

Vector3
Vector3::barycentric(const Vector3 &vec1, const Vector3 &vec2, const Vector3 &vec3, const nfloat amount1, const nfloat amount2)
{
	return (vec1 + (amount1 * (vec2 - vec1)) + (amount2 * (vec3 - vec1)));
}

Vector3
Vector3::catmullRom(const Vector3 &vec1, const Vector3 &vec2, const Vector3 &vec3, const Vector3 &vec4, const nfloat amount)
{
	nfloat num = amount * amount;
	nfloat num2 = amount * num;

	return (0.5f * ((((2.0f * vec2) + ((-vec1 + vec3) * amount)) + (((((2.0f * vec1) - (5.0f * vec2)) + (4.0f * vec3)) - vec4) * num)) + ((((-vec1 + (3.0f * vec2)) - (3.0f * vec3)) + vec4) * num2)));
}

Vector3
Vector3::clamp(const Vector3 &vec, const Vector3 &min, const Vector3 &max)
{
	nfloat x = vec.X;

	x = (x > max.X) ? max.X : x;
	x = (x < min.X) ? min.X : x;

	nfloat y = vec.Y;

	y = (x > max.Y) ? max.Y : y;
	y = (x < min.Y) ? min.Y : y;

	nfloat z = vec.Z;

	z = (z > max.Z) ? max.Z : z;
	z = (z < min.Z) ? min.Z : z;

	return Vector3(x, y, z);
}

Vector3
Vector3::crossProduct(const Vector3 &vec1, const Vector3 &vec2)
{
	return Vector3(
		vec1.Y * vec2.Z - vec1.Z * vec2.Y,
		vec1.Z * vec2.X - vec1.X * vec2.Z,
		vec1.X * vec2.Y - vec1.Y * vec2.X
	);
}

Vector3
Vector3::crossProduct(const Vector3 &vec) const
{
	return Vector3(
		Y * vec.Z - Z * vec.Y,
		Z * vec.X - X * vec.Z,
		X * vec.Y - Y * vec.X
	);
}

nfloat
Vector3::distance(const Vector3 &vec1, const Vector3 &vec2)
{
	return (vec1 - vec2).length();
}

nfloat
Vector3::distance(const Vector3 &vec) const
{
	return (*this - vec).length();
}

nfloat
Vector3::distanceSquared(const Vector3 &vec1, const Vector3 &vec2)
{
	return (vec1 - vec2).lengthSquared();
}

nfloat
Vector3::distanceSquared(const Vector3 &vec) const
{
	return (*this - vec).lengthSquared();
}

nfloat
Vector3::dotProduct(const Vector3 &vec1, const Vector3 &vec2)
{
	return vec1.X * vec2.X + vec1.Y * vec2.Y + vec1.Z * vec2.Z;
}

nfloat
Vector3::dotProduct(const Vector3 &vec) const
{
	return X * vec.X + Y * vec.Y + Z * vec.Z;
}

Vector3
Vector3::hermite(const Vector3 &vec1, const Vector3 &tang1, const Vector3 &vec2, const Vector3 &tang2, const nfloat amount)
{
	nfloat num = amount * amount;
	nfloat num2 = amount * num;
	nfloat num3 = ((2.0f * num2) - (3.0f * num)) + 1.0f;
	nfloat num4 = (-2.0f * num2) + (2.0f * num);
	nfloat num5 = (num2 - (2.0f * num)) + amount;
	nfloat num6 = num2 - num;

	return ((((vec1 * num3) + (vec2 * num4)) + (tang1 * num5)) + (tang2 * num6));
}

bool
Vector3::isPerpendicular(const Vector3 &vec1, const Vector3 &vec2)
{
	return (dotProduct(vec1, vec2) == 0);
}

bool
Vector3::isPerpendicular(const Vector3 &vec) const
{
	return (dotProduct(vec) == 0);
}

nfloat
Vector3::length(const Vector3 &vec)
{
	return sqrt(dotProduct(vec, vec));
}

nfloat
Vector3::length() const
{
	return sqrt(dotProduct(*this));
}

nfloat
Vector3::lengthSquared(const Vector3 &vec)
{
	return dotProduct(vec, vec);
}

nfloat
Vector3::lengthSquared() const
{
	return dotProduct(*this);
}

Vector3
Vector3::lerp(const Vector3 &vec1, const Vector3 &vec2, const nfloat amount)
{
	return (vec1 + ((vec2 - vec1 * amount)));
}

Vector3
Vector3::max(const Vector3 &vec1, const Vector3 &vec2)
{
	nfloat x = qMax(vec1.X, vec2.X);
	nfloat y = qMax(vec1.Y, vec2.Y);
	nfloat z = qMax(vec1.Z, vec2.Z);

	return Vector3(x, y, z);
}

Vector3
Vector3::min(const Vector3 &vec1, const Vector3 &vec2)
{
	nfloat x = qMin(vec1.X, vec2.X);
	nfloat y = qMin(vec1.Y, vec2.Y);
	nfloat z = qMin(vec1.Z, vec2.Z);

	return Vector3(x, y, z);
}

Vector3
Vector3::normalise(const Vector3 &vec)
{
	nfloat len = length(vec);

	if(len == 0.0f) {
		len = 1.0f;
	}

	return vec / len;
}

void
Vector3::normalise()
{
	nfloat len = length();

	if(len == 0.0f) {
		len = 1.0f;
	}

	(*this) /= len;
}

Vector3
Vector3::reflect(const Vector3 &vec, const Vector3 &normal)
{
	nfloat num = vec.dotProduct(normal);

	return (vec - ((2.0f * num) * normal));
}

Vector3
Vector3::smoothStep(const Vector3 &vec1, const Vector3 &vec2, nfloat amount)
{
	amount = (amount > 1.0f) ? 1.0f : ((amount < 0.0f) ? 0.0f : amount);
	amount = (amount * amount) * (3.0f - (2.0f * amount));

	return (vec1 + ((vec2 - vec1) * amount));
}